# 导入系统模块，提供对Python解释器相关功能的访问
import sys

# 从PyQt6导入核心模块
from PyQt6 import QtCore
# 从PyQt6.QtCore导入Qt枚举常量，用于设置对齐方式等属性
from PyQt6.QtCore import Qt
# 从PyQt6.QtWidgets导入GUI组件：应用程序、主窗口、标签、网格布局、按钮和通用窗口部件
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QGridLayout, QPushButton, QWidget
# 从PyQt6.QtGui导入图像处理类：像素图、图像和GUI应用工具
from PyQt6.QtGui import QPixmap, QImage, QGuiApplication
# 导入OpenCV计算机视觉库，用于摄像头捕获和图像处理
import cv2
import numpy as np

# 定义主窗口类，继承自QMainWindow
class MainWindow(QMainWindow):
    # 初始化方法
    def __init__(self):
        # 调用父类QMainWindow的初始化方法
        super().__init__()
        # 设置窗口标题
        self.setWindowTitle('pyqt6显示opencv获取的摄像头图像')
        
        # 创建按钮控件，用于控制摄像头状态
        self.btn_camera = QPushButton('打开摄像头')
        # 创建标签控件，用于显示摄像头图像
        self.lbl_img = QLabel('显示摄像头图像')
        # 设置标签的样式表 - 添加1像素宽的黑色边框
        self.lbl_img.setStyleSheet('border: 1px solid black;')
        # 设置标签内容对齐方式为居中对齐
        self.lbl_img.setAlignment(Qt.AlignmentFlag.AlignCenter)
        # 设置标签最小尺寸为640x480像素（摄像头默认分辨率）
        self.lbl_img.setMinimumSize(640, 480)
        # 连接按钮的点击信号到槽函数btn_camera_click
        self.btn_camera.clicked.connect(self.btn_camera_click)
        
        # 创建顶部容器部件
        top_widget = QWidget()
        # 创建网格布局对象
        grid = QGridLayout()
        # 将图像标签添加到网格布局的第0行第0列，顶部对齐
        grid.addWidget(self.lbl_img, 0, 0, Qt.AlignmentFlag.AlignTop)
        # 将按钮添加到网格布局的第1行第0列，底部对齐
        grid.addWidget(self.btn_camera, 1, 0, Qt.AlignmentFlag.AlignBottom)
        # 将网格布局设置到顶部容器部件
        top_widget.setLayout(grid)
        # 将顶部容器设置为主窗口的中央部件
        self.setCentralWidget(top_widget)

        # 调用窗口居中方法
        self.center_win()

        # 初始化摄像头状态标志（未打开）
        self.is_open_camera = False
        # 初始化OpenCV视频捕获对象
        self.video_cap = None
        # 创建定时器对象，用于定时读取摄像头图像
        self.camera_timer = QtCore.QTimer(self)
        # 连接定时器的超时信号到摄像头播放槽函数
        self.camera_timer.timeout.connect(self.play_camera_video)

    # 函数功能：窗口居中显示
    def center_win(self):
        # 获取窗口的几何形状（位置和大小）
        qr = self.frameGeometry()
        # 获取主屏幕可用区域的中心点
        cp = QGuiApplication.primaryScreen().availableGeometry().center()
        # 将窗口矩形的中心移动到屏幕中心
        qr.moveCenter(cp)
        # 移动窗口到调整后的位置
        self.move(qr.topLeft())

    # 函数功能：处理摄像头按钮点击事件
    def btn_camera_click(self):
        # 判断摄像头是否关闭（按下"打开摄像头"按钮）
        if not self.is_open_camera:
            # 打开默认摄像头（索引0）
            self.video_cap = cv2.VideoCapture(0)
            # 打印摄像头的帧率
            print('camera fps:', self.video_cap.get(cv2.CAP_PROP_FPS))
            # 启动定时器，每100毫秒触发一次
            self.camera_timer.start(100)
            # 更新摄像头状态标志
            self.is_open_camera = True
            # 更新按钮文本
            self.btn_camera.setText('关闭摄像头')
        # 摄像头已打开（按下"关闭摄像头"按钮）
        else:
            # 停止定时器
            self.camera_timer.stop()
            # 释放摄像头资源
            self.video_cap.release()
            # 清空视频捕获对象
            self.video_cap = None
            # 清除标签中的图像
            self.lbl_img.clear()
            # 更新按钮文本
            self.btn_camera.setText('打开摄像头')
            # 更新摄像头状态标志
            self.is_open_camera = False

    # 函数功能：播放摄像头视频流
    def play_camera_video(self):
        # 检查摄像头是否处于打开状态
        if self.is_open_camera:
            # 从摄像头抓取一帧（不解码）
            self.video_cap.grab()
            # 获取抓取的帧并解码
            ret, frame = self.video_cap.retrieve()
            # 检查帧是否成功获取
            if ret:
                # 处理为素描效果
                sketch = process_frame_for_sketch(frame)
                
                # 转换为QPixmap
                pixmap = cv2_to_qpixmap(sketch)
                
                # 缩放以适应标签
                lbl_size = self.lbl_img.size()
                scaled_pixmap = pixmap.scaled(
                    lbl_size, 
                    Qt.AspectRatioMode.KeepAspectRatio, 
                    Qt.TransformationMode.SmoothTransformation
                )
                
                # 显示图像
                self.lbl_img.setPixmap(scaled_pixmap)

def process_frame_for_sketch(frame):
    """将帧转换为素描风格"""
    # 转换为灰度图像
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # 中值滤波去噪
    gray = cv2.medianBlur(gray, 7)
    
    # 拉普拉斯边缘检测
    edges = cv2.Laplacian(gray, cv2.CV_8U, ksize=5)
    
    # 反相二值化
    _, sketch = cv2.threshold(edges, 80, 255, cv2.THRESH_BINARY_INV)
    
    return sketch

def cv2_to_qpixmap(cv_img):
    """将OpenCV图像转换为QPixmap"""
    # 确保图像是连续的
    if not cv_img.flags['C_CONTIGUOUS']:
        cv_img = np.ascontiguousarray(cv_img)
    
    # 获取尺寸
    height, width = cv_img.shape[:2]
    
    # 如果是灰度图像，直接创建QImage
    if len(cv_img.shape) == 2:  # 灰度图
        qimage = QImage(
            cv_img.data,
            width,
            height,
            width,  # 灰度图像每行字节数就是宽度
            QImage.Format.Format_Grayscale8
        )
    else:  # 彩色图像
        height, width, channel = cv_img.shape
        bytes_per_line = 3 * width
        # 确保颜色顺序是RGB（OpenCV默认BGR）
        cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
        qimage = QImage(cv_img.data, width, height, bytes_per_line, QImage.Format.Format_RGB888)
    
    return QPixmap.fromImage(qimage)

# 主程序入口
if __name__ == '__main__':
    # 创建QApplication实例，管理GUI应用程序的控制流
    app = QApplication(sys.argv)
    # 创建主窗口实例
    window = MainWindow()
    # 显示主窗口
    window.show()
    # 启动应用程序的事件循环，等待退出信号
    sys.exit(app.exec())